package com.gitee.srurl.sid.gen;

import cn.hutool.core.util.StrUtil;
import com.gitee.srurl.register.DataReportHandle;
import com.gitee.srurl.register.IDataReport;
import com.gitee.srurl.sid.domain.SidConstant;
import com.gitee.srurl.sid.domain.SidData;
import com.gitee.srurl.sid.domain.SidReportData;
import com.gitee.srurl.sid.util.SidTimestampHelper;
import lombok.extern.slf4j.Slf4j;

/**
 * @author zxw
 * @date 2023/4/25 22:18
 */
@Slf4j
public class Sid implements IDataReport {

    /**
     * 是否允许时钟回拨
     */
    private final Boolean allowClockBackwards;

    private final Long startYear;

    /**
     * 当前是否处于时钟回拨
     */
    private volatile boolean cloakBackwards;

    private final SidTimestampHelper sidTimestampHelper;

    /**
     * 最新数据时间
     */
    private volatile long lastTime = -1L;

    /**
     * 最新数据时间（使用24-31之间时钟的ID）
     */
    private volatile long lastTimeBorrow = -1L;

    /**
     * 自增序号
     */
    long sequence = 0L;

    /**
     * 借用时间自增序号
     */
    long sequenceBorrow = 0L;

    public Sid() {
        this(null);
    }

    public Sid(SidData sidData) {
        sidData = sidData == null ? SidData.builder().build() : sidData;
        this.startYear = sidData.getStartYear();
        this.allowClockBackwards = sidData.getAllowClockBackwards();
        this.lastTime = sidData.getLastTime();
        this.lastTimeBorrow = sidData.getLastTimeBorrow();
        this.sequence = sidData.getSequence();
        this.sequenceBorrow = sidData.getSequenceBorrow();
        this.sidTimestampHelper = new SidTimestampHelper(this.startYear);
        // 注册上报
        DataReportHandle.registerReport(this);
    }

    public synchronized long nextId() {

        long currentTime = this.sidTimestampHelper.now();
        if (this.cloakBackwards) {
            if (currentTime >= this.lastTime) {
                // 说明时钟正常
                this.cloakBackwards = false;
            }
        }

        // 发生时钟回拨
        if (currentTime < this.lastTime) {
            // 设置不允许回拨
            if (!this.allowClockBackwards) {
                throw new IllegalStateException("时钟回拨，当前不允许生成ID");
            }

            if (this.lastTimeBorrow < this.sidTimestampHelper.theDayOfMaxNormal()) {
                // 说明是第一次
                this.lastTimeBorrow = this.sidTimestampHelper.theDayOfMaxNormal() + 1;
            }

            final long sequenceBorrow = (this.sequenceBorrow + 1) & SidConstant.SEQUENCE_MASK;
            if (sequenceBorrow == 0) {
                this.lastTimeBorrow = this.lastTimeBorrow + 1;
                this.sequenceBorrow = 0L;
                if (this.lastTimeBorrow > this.sidTimestampHelper.theDayOfMaxBorrow()) {
                    // 说明当天时间用完
                    log.error("发生时钟回拨，当天时间已用尽");
                    throw new IllegalStateException("当天时间已用尽");
                }
                this.sequenceBorrow = 1L;
            } else {
                this.sequenceBorrow = sequenceBorrow;
            }
            return this.lastTimeBorrow << 20 | this.sequenceBorrow;
        } else {
            if (currentTime == this.lastTime) {
                final long sequence = (this.sequence + 1) & SidConstant.SEQUENCE_MASK;
                if (sequence == 0) {
                    log.info("当前时间内ID已用尽:{}", this.lastTime);
                    throw new IllegalStateException("当前时间内ID已用尽");
                }
                this.sequence = sequence;
            } else {
                this.sequence = 0L;
                this.lastTime = currentTime;
            }
            return this.lastTime << 20 | this.sequence;
        }
    }

    @Override
    public String report() {

        SidReportData reportData = SidReportData.builder()
                .startYear(this.startYear)
                .lastTime(this.lastTime)
                .lastTimeBorrow(this.lastTimeBorrow)
                .sequence(this.sequence)
                .sequenceBorrow(this.sequenceBorrow)
                .version(System.currentTimeMillis())
                .build();

        return reportData.toString();
    }

    @Override
    public String reportPath() {
        return StrUtil.format("sid/sid.json");
    }
}
